#from SocketServer import *
#import socket

# Convert human readable form to 32 bit value
#packed_ip = socket.inet_aton("208.146.240.1")
# Convert 32 bit value to ip adress
#ip_adress = socket.inet_ntoa(packed_ip)

import socket
import struct
import sys
import time
import SocketServer
from  threading import *
import binascii
from array import *
from datetime import datetime
#from event import event

RENDEZVOUS_REGISTER = 1 # If using 'networks' , only master peer (network owner) is kept registered all times
                    # other peers, if connected to a network, unregister themselfs from RENDEZVOUS server
RENDEZVOUS_UNREGISTER = 2
RENDEZVOUS_PEERS = 3 #List of peers . Implement 'networks' like hamachi.
RENDEZVOUS_NEW_PEERS = 4 #New peer(s) since this peer registered
#These prolly need to have IP:PORT from sender :P
RENDEZVOUS_DATA = 5 #Expect alot of data from buffer
RENDEZVOUS_PING = 6
RENDEZVOUS_ACK = 7

#Packet structure : 4:packet_size , 4:message_type [, 4:crc32 ], x:data
BUFFER_LENGTH = 1024 # overal data size is BUFFER_LENGTH - 8 (data length + message type)
    #Also, if you have more than or equal to 1024 , send an extra null sized packed so that 
    #peer would no that was the last one. 
    
    #Also implement packet ordering etc. like something tcp does. :P

class State:
    def __init__(self,status,timeout=None,serial=None):
        self.status = status
        self.timeout = timeout
        self.time = datetime.now()
        self.count = 0
        self.serial = serial
        self.event = Event()
        
    def increment(self):
        self.count += 1
        self.time = datetime.now()
        
    def delta(self):
        return datetime.now() - self.time
        
#For peers to send/receive data from each other
class Client:
    def __init__(self,local_addr, serv_addr, name):
        self.serv_addr = (serv_addr,('0.0.0.0',0))
        self.local_addr = local_addr
        self.name = name
        self.sock = None
        self.cb_reg = None
        self.cb_conn = None
        self.cb_disconn = None
        self.cb_datarecv = None
        self.thread = None
        self.event = Event()
        self.peers = []
        self.send_state = {} #Has other peer acknowledge receiving the packet or not
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        self.sock.settimeout(30)
        self.sock.bind(local_addr)
        ####self.sock.connect(self.serv_addr)
        self.thread = Thread(target = self.recv, name = 'thread_'+self.name)
        self.thread.setDaemon(True) #Die if main thread exits
        self.thread.start()
        
    def connect(self):
        self.send(RENDEZVOUS_REGISTER,self.encode_addr(self.local_addr),self.serv_addr)
                
    def disconnect(self):
        self.send(RENDEZVOUS_UNREGISTER,self.encode_addr(self.local_addr),self.serv_addr,False)
        #self.sock.close()
    
    def encode_addr(self,addr):
        ip = socket.inet_aton(addr[0])
        port = struct.pack("H",addr[1])
        return  ip+port

    def decode_addr(self,addr):
        ip = socket.inet_ntoa(addr[0:4])
        port = struct.unpack("H",addr[4:6])[0]
        return  (ip,port)
        
    def get_peer_list(self):
        self.send(RENDEZVOUS_PEERS,None,self.serv_addr)
        
    def send_client(self,idx,data_block):
        self.send(RENDEZVOUS_DATA, data_block, self.peers[idx])
        return
        data = ""
        size = 0
        crc = 0
        if data_block:
            data = data_block
            size = len(data)
        
        crc = binascii.crc32(data,crc)
        packed_crc = struct.pack("i",crc)
        #print "\tC CRC:", crc
        
        packed_type = struct.pack("i",RENDEZVOUS_DATA)
        data = struct.pack("i",size + struct.calcsize("iii")) + packed_type + packed_crc + data
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.sendto(data,('0.0.0.0',32324))
        
    def send(self,type,data_block,peer,wait=True):
        if not self.sock:
            return
        print "Sending data to",peer,len(peer), "from",self.local_addr
        
        data = ""
        size = 0
        crc = 0
        if data_block:
            data = data_block
            size = len(data)
        
        crc = binascii.crc32(data,crc)
        packed_crc = struct.pack("i",crc)
        #print "\tC CRC:", crc
        
        packed_type = struct.pack("i",type)
        data = struct.pack("i",size + struct.calcsize("iii")) + packed_type + packed_crc + data
        
        self.event.clear()
        
        count = 0
        while count <3 and not self.event.isSet():
            print peer[0]
            ret = self.sock.sendto(data,peer[0]) 
            #data should be <= BUFFER_LENGTH
            count += 1
            if wait:
                self.event.wait(5)
                print "Wait ",self.local_addr,ret,self.event.isSet()
            else:
                break
        
        if count >= 3 and len(peer)>1 and not self.event.isSet():
            count = 0
            print "Trying local"
            while count < 3 and peer[1][1]>0:
                print "Peer",peer[1]
                ret = self.sock.sendto(data,peer[1]) #data should be <= BUFFER_LENGTH
                count += 1
                if wait:
                    self.event.wait(5)
                    print ret,self.event.isSet()
                else:
                    break
                
    def recv(self):
        
        while True:
            if not self.sock:
                break

            crc = 0
            try:
                (msg,src) = self.sock.recvfrom(BUFFER_LENGTH) #data length
            except socket.timeout:
                continue
            except:
                #print "Error on Receive, thread exiting"
                return
            
            self.event.set()
                
            if len(msg)<=0:
                continue
            print "\tC from",src
            
            length = struct.unpack("i",msg[:4])[0]
            #print "\tC Length:",length
            
            type = struct.unpack("i",msg[4:8])[0]
            #print "\tC Type:",type
            
            crc = struct.unpack("i",msg[8:12])[0]
            #print "\tC CRC:",crc
            
            if type == RENDEZVOUS_REGISTER:
                print "\tC Peer got registered",msg[12:]
                if self.cb_reg:
                    self.cb_reg(1)
                
            if type == RENDEZVOUS_UNREGISTER:
                print "\tC Peer is unregistered",msg[12:]
                if self.cb_reg:
                    self.cb_reg(0)
                    
            if type == RENDEZVOUS_PEERS:
                peer_cnt = ( length - 12 ) / 6 / 2
                print "\tC Peer list count",peer_cnt
                for i in range(0,peer_cnt):
                    i_off = (12 + i*12)
                    p_off = (12 + 4 + i*12)
                    ip = socket.inet_ntoa(msg[i_off:i_off+4])
                    port = struct.unpack("H",msg[p_off:p_off+2])[0]
                    
                    ip_local = socket.inet_ntoa(msg[i_off+6:i_off+10])
                    port_local = struct.unpack("H",msg[p_off+6:p_off+8])[0]
                    
                    self.peers.append( ((ip,port),(ip_local,port_local)) )
                #print "\t",self.peers
                
            if type == RENDEZVOUS_DATA:
                print "\tC Data:",msg[12:] ,"To:",src
                self.send(RENDEZVOUS_ACK,'OK',(src,))
            



#Server class , registers peers and gives them info about other peers
class MyUDPServer(SocketServer.UDPServer):
    def __init__(self,a,b):
        SocketServer.UDPServer.__init__(self,a,b)
        self.peers = []
        
    def process_request(self,request,client_address):
        print "Call from:",client_address
        SocketServer.UDPServer.process_request(self,request,client_address)
        #l = struct.unpack("i",request[0])
        #self.RequestHandlerClass(request,client_address,self)
    

class Server:
    class handler(SocketServer.DatagramRequestHandler):
        #def __init__(self,a,b,c,type,length):
        #    SocketServer.DatagramRequestHandler.__init__(self,a,b,c)
        #    self.length = length
        #    self.type = type
            
        def handle(self):
            
            length = struct.unpack("i",self.request[0][:4])[0]
            #print "Length:",length
            type = struct.unpack("i",self.request[0][4:8])[0]
            print "Type:",type
            crc = struct.unpack("i",self.request[0][8:12])[0]
            #print "CRC:",crc
            
            if type == RENDEZVOUS_REGISTER:
                local = self.decode_addr(self.request[0][12:18])
                print "Server register:",self.client_address,":",local
                if not (self.client_address,local) in self.server.peers :
                    self.server.peers.append((self.client_address,local))
                    #print "Peers:",self.server.peers
                else:
                    print "Already registered"
                
                self.send(RENDEZVOUS_REGISTER,'OK')
                
            if type == RENDEZVOUS_UNREGISTER:
                local = self.decode_addr(self.request[0][12:18])
                print "Server unregister:",self.client_address,local
                #print "Peers:",self.server.peers
                if (self.client_address,local) in self.server.peers:
                    self.server.peers.remove((self.client_address,local))
                self.send(RENDEZVOUS_UNREGISTER,'OK')
            
            if type == RENDEZVOUS_PEERS:
                data = ''
                peer_count = len(self.server.peers)
                #168 * (4 + 2 ) = 1008 bytes + 12 = 1020
                cnt_offset = 0
                cnt = 168 / 2
                if cnt+cnt_offset > len(self.server.peers):
                    cnt = len(self.server.peers)
                    
                for i in range(cnt_offset, cnt):
                    print self.server.peers[i][1]
                    #remote ip
                    data += socket.inet_aton(self.server.peers[i][0][0]) + struct.pack("H",self.server.peers[i][0][1])
                    #local ip
                    data += socket.inet_aton(self.server.peers[i][1][0]) + struct.pack("H",self.server.peers[i][1][1])
                self.send(RENDEZVOUS_PEERS, data)
            
            if type == RENDEZVOUS_DATA:
                print "Data:",self.request[0][12:]
                self.send(RENDEZVOUS_DATA,"OK")



        def encode_addr(self,addr):
            ip = socket.inet_aton(addr[0])
            port = struct.pack("H",addr[1])
            return  ip+port

        def decode_addr(self,addr):
            ip = socket.inet_ntoa(addr[0:4])
            port = struct.unpack("H",addr[4:6])[0]
            return  (ip,port)
        
        def send(self,type,data_block):
            data = ""
            size = 0
            crc = 0
            if data_block:
                data = data_block
                size = len(data)
            
            crc = binascii.crc32(data,crc)
            packed_crc = struct.pack("i",crc)
            #print "CRC:", crc,"Size:",size
            
            packed_type = struct.pack("i",type)
            data = struct.pack("i",size + struct.calcsize("iii")) + packed_type + packed_crc + data

            self.wfile.write(data) 
            #data should be <= BUFFER_LENGTH
    
    def __init__(self,port):
        self.server = MyUDPServer(('',port), self.handler)
        self.thread = Thread(target=self.server.serve_forever,name='udpserv')
        self.thread.setDaemon(True)
        self.thread.start()
        
    def close(self):
        self.server.server_close()


if __name__ == "__main__":
    s = Server(30001)
    
    c0 = Client( ('',32324), ('affenas',30001),'aff')
    c0.connect()
    
    print "--------------"
    c1 = Client(('',6342),('affenas',30001),'kopi')
    c1.connect()
    
    print "--------------"
    
    c1.get_peer_list()
    
    print "--------------"
    c1.send_client(0,"LOLOLOLOLOL")
    
    print "--------------"
    c0.disconnect()
    
    print "--------------"
    c1.disconnect()
    time.sleep(1)
    s.close()
